<template>
  <MLayoutPage :classes="`shadow-1 rounded-borders qx-tree ${classes || ''}`" :maxHeight="maxHeight" topClass="q-pb-sm">
    <template v-slot:top>
      <div class="qx-tree-title">
        <slot name="tree-top"></slot>
      </div>
      <div class="row items-center q-gutter-x-sm">
        <q-input v-model="filter" class="col" v-bind="inputBind"></q-input>
        <q-btn color="primary" icon="mdi-swap-vertical" outline padding=".375rem .75rem" unelevated
               @click="toggleExpand"></q-btn>
      </div>

      <template v-if="multiple && showWay">
        <q-option-group v-model="tickStrategy" :options="checkWays" v-bind="groupBind" />
      </template>
    </template>

    <q-tree v-if="multiple" ref="QxTree" v-model:expanded="expanded" v-model:ticked="ticked"
            :filter-method="filterMethod" :no-transition="noTransition" :nodes="dicData" :tick-strategy="tickStrategy"
            v-bind="treeBind" @update:ticked="updateTicked">
      <template v-if="slots.header" v-slot:default-header="prop">
        <slot :prop="prop" name="header"></slot>
      </template>
      <template v-if="slots.body" v-slot:default-body="prop">
        <slot :prop="prop" name="body"></slot>
      </template>
    </q-tree>

    <q-tree v-else ref="QxTree" v-model:expanded="expanded" v-model:selected="selected" :filter-method="filterMethod"
            :no-transition="noTransition" :nodes="dicData" selected-color="primary" v-bind="treeBind"
            @update:selected="updateSelected">
      <template v-if="slots.header" v-slot:default-header="prop">
        <slot :prop="prop" name="header"></slot>
      </template>
      <template v-if="slots.body" v-slot:default-body="prop">
        <slot :prop="prop" name="body"></slot>
      </template>
    </q-tree>

    <q-inner-loading :showing="innerLoading">
      <q-spinner-ios color="primary" size="50px" />
    </q-inner-loading>
  </MLayoutPage>
</template>

<script setup>
import { i18n } from 'boot/i18n'
import { getAPI, postAPI } from 'src/api'
import { deepClone } from 'src/utils/utils'
import MLayoutPage from 'components/Global/MLayoutPage.vue'
import { computed, nextTick, reactive, ref, toRef, useSlots, watch } from 'vue'
import QTree from './QTree'

const prop = defineProps({
  field: String,
  props: Object,
  options: Array,
  dicUrl: String,
  classes: String,
  rowInfo: Object,
  dicQuery: Object,
  addName: Boolean,
  change: Function,
  showWay: Boolean,
  loading: Boolean,
  strategy: String,
  multiple: Boolean,
  dicMethods: String,
  filterMethod: Function,
  noSelectionUnset: Boolean,
  defaultExpandAll: Boolean,
  maxHeight: [String, Number],
  modelValue: [String, Array],
  duration: { type: Number, default: 300 },
  accordion: { type: Boolean, default: true }
})

const emits = defineEmits(['change', 'getNodes', 'update:modelValue', 'update:loading'])

const filter = ref('')
const QxTree = ref(null)
const noTransition = ref(!prop.duration)
const tickStrategy = ref(prop.strategy || 'leaf')
const checkWays = computed(() => {
  return [{
    label: `${i18n.t('CheckWayTip1')}`, value: 'strict'
  }, {
    label: `${i18n.t('CheckWayTip2')}`, value: 'leaf'
  }, {
    label: `${i18n.t('CheckWayTip3')}`, value: 'leaf-filtered'
  }]
})

const inputBind = computed(() => {
  return { clearable: true, dense: true, placeholder: i18n.t('FilterKeywords'), outlined: true }
})
const groupBind = reactive({ class: 'qx-tree-group q-pt-sm q-pl-sm', dense: true, inline: true })
const treeBind = computed(() => {
  const { label: labelKey = 'label', key: nodeKey = 'id', children: childrenKey = 'children' } = prop.props || {}
  return {
    accordion: prop.accordion, childrenKey, labelKey, nodeKey, filter: filter.value, class: 'q-px-md',
    noSelectionUnset: prop.noSelectionUnset, 'default-expand-all': prop.defaultExpandAll
  }
})

const slots = useSlots()
const ticked = ref([])
const dicData = ref([])
const selected = ref('')
const expanded = ref([])
const innerLoading = ref(false)

const setInitExpand = () => {
  nextTick(() => {
    if (prop.defaultExpandAll && QxTree.value) return QxTree.value.expandAll()
    if (!prop.multiple && QxTree.value) {
      const node = QxTree.value.getNodeByKey(selected.value)
      if (node) return (expanded.value = node.parentIds ? node.parentIds.split(',') : [])
    }
    if (dicData.value.length && !expanded.value.length) expanded.value = [dicData.value[0][treeBind.value.nodeKey]]
  })
}

const initData = () => {
  if (prop.multiple) {
    if (prop.modelValue !== ticked.value) ticked.value = prop.modelValue || []
  } else {
    if (prop.modelValue !== selected.value) selected.value = prop.modelValue || ''
  }
  if (!expanded.value.length) setInitExpand()
}

const initOptions = () => {
  if (prop.dicUrl) {
    innerLoading.value = true
    const query = deepClone(prop.dicQuery || {})
    API.value(prop.dicUrl, query, data => {
      dicData.value = data.rows || data || []
      innerLoading.value = false
      emits('update:loading', innerLoading.value)
      initData()
    }, () => {
      innerLoading.value = false
      emits('update:loading', innerLoading.value)
    })
    return
  }
  if (prop.loading) innerLoading.value = prop.loading
  dicData.value = deepClone(prop.options || [])
  initData()
}

watch(toRef(prop, 'modelValue'), () => initData())
watch(toRef(prop, 'loading'), () => (innerLoading.value = prop.loading))
watch([toRef(prop, 'options'), toRef(prop, 'dicQuery')], () => initOptions(), { immediate: true })

const API = computed(() => prop.dicMethods === 'get' ? getAPI : postAPI)
const formInfo = toRef(prop, 'rowInfo')
const formatFormInfo = nodes => {
  if (!formInfo.value) return
  if (prop.addName) {
    if (prop.multiple) formInfo.value[`SSS_${prop.field}`] = nodes.map(node => node[treeBind.value.labelKey]).join(',')
    else formInfo.value[`SSS_${prop.field}`] = nodes[treeBind.value.labelKey]
  }
}

const toggleExpand = () => {
  if (!QxTree.value) return
  noTransition.value = true
  if (expanded.value.length) QxTree.value.collapseAll()
  else QxTree.value.expandAll()
  nextTick(() => (noTransition.value = !prop.duration))
}

const updateTicked = target => {
  if (tickStrategy.value === 'strict') {
    nextTick(() => {
      const nodes = QxTree.value.getTickedNodes(), nodeKeys = Object.values(QxTree.value.meta.value) || []
      if (nodes) formatFormInfo(nodes)
      emits('update:modelValue', target)
      if (prop.change) prop.change(target, nodes, nodeKeys.map(el => el.key))
      else emits('change', target, nodes, nodeKeys.map(el => el.key))
    })
    return
  }
  nextTick(() => {
    const nodeKeys = Object.values(QxTree.value.meta.value), nodes = [], keys = []
    for (const el of nodeKeys) {
      if (el.ticked || el.indeterminate) {
        nodes.push(QxTree.value.getNodeByKey(el.key))
        keys.push(el.key)
      }
    }
    ticked.value = keys
    if (nodes) formatFormInfo(nodes)
    emits('update:modelValue', keys)
    if (prop.change) prop.change(keys, nodes, nodeKeys.map(el => el.key))
    else emits('change', keys, nodes, nodeKeys.map(el => el.key))
  })
}

const updateSelected = target => {
  nextTick(() => {
    const node = QxTree.value.getNodeByKey(target)
    if (node) formatFormInfo(node)
    emits('update:modelValue', target)
    if (prop.change) prop.change(target, node)
    else emits('change', target, node)
  })
}

const done = () => {
  innerLoading.value = false
  emits('update:loading', innerLoading.value)
}

const getNodes = () => {
  innerLoading.value = true
  tickStrategy.value = 'strict'
  nextTick(() => emits('getNodes', QxTree.value.getTickedNodes(), done))
}

defineExpose({ getNodes, updateSelected, updateTicked })

</script>